/*
 * @Descripttion: xxx
 * @version: 1.0.0
 * @Date: 2022-06-14 17:07:39
 * @LastEditTime: 2022-06-21 15:25:18
 */

import type { IPoint2D } from "../CustomCesium"
import bezier from "@turf/bezier-spline"
import * as turf from "@turf/helpers"
import { Cartesian3 } from "cesium"
import _ from "lodash"
import { Constant, MathUtils } from "../CustomCesium"

// 直线箭头默认参数
const fineArrowDefualParam = {
  tailWidthFactor: 0.15,
  neckWidthFactor: 0.2,
  headWidthFactor: 0.25,
  headAngle: Math.PI / 8.5,
  neckAngle: Math.PI / 13
}

// 双箭头默认参数
const doubleArrowDefualParam = {
  type: "doublearrow",
  headHeightFactor: 0.25,
  headWidthFactor: 0.3,
  neckHeightFactor: 0.85,
  fixPointCount: 4,
  neckWidthFactor: 0.15
}

// 尾迹箭头默认参数
const tailedAttackArrowDefualParam = {
  headHeightFactor: 0.18,
  headWidthFactor: 0.3,
  neckHeightFactor: 0.85,
  neckWidthFactor: 0.15,
  tailWidthFactor: 0.1,
  headTailFactor: 0.8,
  swallowTailFactor: 1
}
export default class PlotUtils {
  /**
   * @descripttion: 贝塞尔曲线
   * @param {IPoint2D} points 轨迹
   * @param {object} options 参数
   * @return {*}
   */
  static bezierCurve(
    points: IPoint2D[],
    options: { resolution: number, sharpness: number } = {
      resolution: 10000,
      sharpness: 0.85
    }
  ): Array<Cartesian3> {
    const length = points.length
    const bezierLinePoints: number[] = []

    if (length <= 2)
      return points.map(point => Cartesian3.fromDegrees(point[0], point[1]))

    const line = turf.lineString(points)
    const curved = bezier(line, options)

    curved.geometry.coordinates.forEach((point) => {
      bezierLinePoints.push(point[0], point[1])
    })

    return Cartesian3.fromDegreesArray(bezierLinePoints)
  }

  /**
   * @descripttion: 直线箭头
   * @param {Array<number>} tailPoint 尾迹点
   * @param {Array<number>} headerPoint 顶部点
   * @return {Array<Cartesian3>}
   */
  static straightArrow(
    tailPoint: IPoint2D,
    headerPoint: IPoint2D
  ): Array<Cartesian3> {
    // 箭头参数
    const tailWidthFactor = fineArrowDefualParam.tailWidthFactor
    const neckWidthFactor = fineArrowDefualParam.neckWidthFactor
    const headWidthFactor = fineArrowDefualParam.headWidthFactor
    const headAngle = fineArrowDefualParam.headAngle
    const neckAngle = fineArrowDefualParam.neckAngle
    // 克隆路径
    const path = [_.cloneDeep(tailPoint), _.cloneDeep(headerPoint)]
    // 箭头关键点
    // 尾部
    const tailPnt = path[0]
    // 头部
    const headerPnt = path[1]
    // 路径长度
    const distance = MathUtils.getBaseLength(path)
    // 尾部宽度因子
    const tFactor = distance * tailWidthFactor
    // 脖子宽度银子
    const nFactor = distance * neckWidthFactor
    // 头部宽度因子
    const hFactor = distance * headWidthFactor
    // 尾->头，顺时针旋转90度
    const a = MathUtils.getThirdPoint(
      headerPnt,
      tailPnt,
      Constant.HALF_PI,
      tFactor,
      !0
    )
    // 尾->头，逆时针旋转90度
    const l = MathUtils.getThirdPoint(
      headerPnt,
      tailPnt,
      Constant.HALF_PI,
      tFactor,
      !1
    )
    // 头->尾，逆时针旋转默认参数
    const u = MathUtils.getThirdPoint(tailPnt, headerPnt, headAngle, hFactor, !1)
    // 头->尾，顺时针旋转默认参数
    const c = MathUtils.getThirdPoint(tailPnt, headerPnt, headAngle, hFactor, !0)
    // 头->尾，逆时针旋转默认参数
    const p = MathUtils.getThirdPoint(tailPnt, headerPnt, neckAngle, nFactor, !1)
    // 头->尾，顺时针旋转默认参数
    const h = MathUtils.getThirdPoint(tailPnt, headerPnt, neckAngle, nFactor, !0)
    // 箭头关键点集合
    const d: Array<number> = []

    d.push(
      a[0],
      a[1],
      p[0],
      p[1],
      u[0],
      u[1],
      headerPnt[0],
      headerPnt[1],
      c[0],
      c[1],
      h[0],
      h[1],
      l[0],
      l[1],
      tailPnt[0],
      tailPnt[1]
    )
    return Cartesian3.fromDegreesArray(d)
  }

  /**
   * @descripttion: 钳击箭头
   * @param {IPoint2D} inputPoints 关键点位
   * @return {*}
   */
  static pincerArrow(inputPoints: IPoint2D[]): Array<Cartesian3> {
    // 深拷贝输入点
    const points = _.cloneDeep(inputPoints)
    // 坐标数量
    const count = points.length
    // 箭头关键点
    // 连接点
    let connPoint: IPoint2D | null = null
    // 左箭头顶点
    let leftArrowVertex: IPoint2D | null = null
    // 箭头点位集合
    let polygonalPoints: Cartesian3[] = []

    if (!(count < 2)) {
      if (count == 2) return polygonalPoints

      // 第一个点位，尾部左侧
      const leftTailPnt = points[0]
      // 第二个点位，尾部右侧
      const rightTailPnt = points[1]
      // 第三个点位，右箭头顶点
      const rightArrowVertex = points[2]

      // 下面的是移动点位后的坐标
      count == 3
        ? (leftArrowVertex = this.getTempPoint4(
            leftTailPnt,
            rightTailPnt,
            rightArrowVertex
          ))
        : (leftArrowVertex = points[3]),
      count == 3 || count == 4
        ? (connPoint = MathUtils.mid(
            MathUtils.mid(leftTailPnt, rightTailPnt),
            MathUtils.mid(leftArrowVertex, rightArrowVertex)
          ))
        : (connPoint = points[4])

      // 左右侧箭头，关键点
      let leftArrowPnts: IPoint2D[], rightArrowPnts: IPoint2D[]
      // 判断顺逆时针
      const isClockWise = MathUtils.isClockWise(
        leftTailPnt,
        rightTailPnt,
        rightArrowVertex
      )
      isClockWise
        ? ((leftArrowPnts = this.getArrowPoints(
            leftTailPnt,
            connPoint,
            leftArrowVertex,
            !1
          )),
          (rightArrowPnts = this.getArrowPoints(
            connPoint,
            rightTailPnt,
            rightArrowVertex,
            !0
          )))
        : ((leftArrowPnts = this.getArrowPoints(
            rightTailPnt,
            connPoint,
            rightArrowVertex,
            !1
          )),
          (rightArrowPnts = this.getArrowPoints(
            connPoint,
            leftTailPnt,
            leftArrowVertex,
            !0
          )))

      const i = leftArrowPnts.length
      const s = (i - 5) / 2
      // 左侧箭头，内侧尾部点位
      const leftArrowTailPnts = leftArrowPnts.slice(0, s - 1)
      const leftArrowHeadPnts = leftArrowPnts.slice(s, s + 5)
      // 左侧箭头，外侧点位
      const leftArrowNeckPnts = leftArrowPnts.slice(s + 5, i)
      // 左侧箭头，外侧点位贝塞尔插值曲线
      const leftArrowNeckCurve = MathUtils.getThreeBezier(leftArrowNeckPnts)
      // 右侧箭头，外侧点位
      const rightArrowNeckPnts = rightArrowPnts.slice(0, s)
      const rightArrowHeadPnts = rightArrowPnts.slice(s, s + 5)
      // 右侧箭头，内侧尾部点位
      const rightArrowTailPnts = rightArrowPnts.slice(s + 5 + 1, i)
      // 右侧箭头，外侧点位贝塞尔插值曲线
      const rightArrowNeckCurve = MathUtils.getThreeBezier(rightArrowNeckPnts)
      // 双箭头中部贝塞尔插值曲线
      const midCurve = MathUtils.getThreeBezier(
        rightArrowTailPnts.concat(leftArrowTailPnts)
      )
      // 双箭头点位
      const wholeCurve = rightArrowNeckCurve.concat(
        rightArrowHeadPnts,
        midCurve,
        leftArrowHeadPnts,
        leftArrowNeckCurve
      )
      const newArray = this.array2Dto1D<IPoint2D, number>(wholeCurve)

      polygonalPoints = Cartesian3.fromDegreesArray(newArray)
    }

    return polygonalPoints
  }

  /**
   * @descripttion: 尾迹攻击箭头
   * @param {Array} inputPoints 点迹
   * @return {*}
   */
  static tailedAttackArrow(inputPoints: Array<IPoint2D>): Array<Cartesian3> {
    // 拷贝点位
    const points = this.dereplication(inputPoints)
    // 点位个数
    const count = points.length
    // 尾部宽度因子
    const tailWidthFactor = tailedAttackArrowDefualParam.tailWidthFactor
    // 燕尾因子
    const swallowTailFactor = tailedAttackArrowDefualParam.swallowTailFactor
    // 燕尾点位
    let swallowTailPnt: IPoint2D
    // 箭头轨迹
    let polygonalPoints: Cartesian3[] = []

    if (!(count < 2)) {
      if (count == 2) return polygonalPoints

      // 尾部点位
      // 左侧
      let tailLeftPnt = points[0]
      // 右侧
      let tailRightPnt = points[1]
      // 判断左右点位，位置是否正确
      MathUtils.isClockWise(points[0], points[1], points[2])
      && ((tailLeftPnt = points[1]), (tailRightPnt = points[0]))

      const n = MathUtils.mid(tailLeftPnt, tailRightPnt)
      const g = [n].concat(points.slice(2))
      const i = this.getAttackArrowHeadPoints(g, tailLeftPnt, tailRightPnt)
      const s = i[0]
      const a = i[4]
      const l = MathUtils.distance(tailLeftPnt, tailRightPnt)
      const u = MathUtils.getBaseLength(g)
      const c = u * tailWidthFactor * swallowTailFactor
      swallowTailPnt = MathUtils.getThirdPoint(g[1], g[0], 0, c, !0)

      const p = l / u
      const h = this.getAttackArrowBodyPoints(g, s, a, p)
      const t = h.length
      let d = [tailLeftPnt].concat(h.slice(0, t / 2))
      d.push(s)

      let f = [tailRightPnt].concat(h.slice(t / 2, t))
      let newArray: number[] = []
      f.push(a),
      (d = this.getQBSplinePoints(d)),
      (f = this.getQBSplinePoints(f)),
      (newArray = this.array2Dto1D(
        d.concat(i, f.reverse(), [swallowTailPnt, d[0]])
      ))

      polygonalPoints = Cartesian3.fromDegreesArray(newArray)
    }
    return polygonalPoints
  }

  private static getTempPoint4(t: IPoint2D, o: IPoint2D, e: IPoint2D) {
    let r: IPoint2D, n: number, g: number, i: IPoint2D
    const s = MathUtils.mid(t, o)
    const a = MathUtils.distance(s, e)
    const l = MathUtils.getAngleOfThreePoints(t, s, e)

    return (
      l < Constant.HALF_PI
        ? ((n = a * Math.sin(l)),
          (g = a * Math.cos(l)),
          (i = MathUtils.getThirdPoint(t, s, Constant.HALF_PI, n, !1)),
          (r = MathUtils.getThirdPoint(s, i, Constant.HALF_PI, g, !0)))
        : l >= Constant.HALF_PI && l < Math.PI
          ? ((n = a * Math.sin(Math.PI - l)),
            (g = a * Math.cos(Math.PI - l)),
            (i = MathUtils.getThirdPoint(t, s, Constant.HALF_PI, n, !1)),
            (r = MathUtils.getThirdPoint(s, i, Constant.HALF_PI, g, !1)))
          : l >= Math.PI && l < 1.5 * Math.PI
            ? ((n = a * Math.sin(l - Math.PI)),
              (g = a * Math.cos(l - Math.PI)),
              (i = MathUtils.getThirdPoint(t, s, Constant.HALF_PI, n, !0)),
              (r = MathUtils.getThirdPoint(s, i, Constant.HALF_PI, g, !0)))
            : ((n = a * Math.sin(2 * Math.PI - l)),
              (g = a * Math.cos(2 * Math.PI - l)),
              (i = MathUtils.getThirdPoint(t, s, Constant.HALF_PI, n, !0)),
              (r = MathUtils.getThirdPoint(s, i, Constant.HALF_PI, g, !1))),
      r
    )
  }

  private static getArrowPoints(
    t: IPoint2D,
    o: IPoint2D,
    e: IPoint2D,
    r: boolean
  ) {
    const n = MathUtils.mid(t, o)
    const g = MathUtils.distance(n, e)
    let i = MathUtils.getThirdPoint(e, n, 0, 0.3 * g, !0)
    let s = MathUtils.getThirdPoint(e, n, 0, 0.5 * g, !0);
    (i = MathUtils.getThirdPoint(n, i, Constant.HALF_PI, g / 5, r)),
    (s = MathUtils.getThirdPoint(n, s, Constant.HALF_PI, g / 4, r))

    const a = [n, i, s, e]
    const l = this.getArrowHeadPoints(a)
    const u = l[0]
    const c = l[4]
    const p = MathUtils.distance(t, o) / MathUtils.getBaseLength(a) / 2
    const h = this.getArrowBodyPoints(a, u, c, p)
    const d = h.length
    let f = h.slice(0, d / 2)
    let E = h.slice(d / 2, d)
    return (
      f.push(u),
      E.push(c),
      (f = f.reverse()),
      f.push(o),
      (E = E.reverse()),
      E.push(t),
      f.reverse().concat(l, E)
    )
  }

  private static getArrowHeadPoints(t: IPoint2D[]) {
    const headHeightFactor = doubleArrowDefualParam.headHeightFactor
    const headWidthFactor = doubleArrowDefualParam.headWidthFactor
    const neckHeightFactor = doubleArrowDefualParam.neckHeightFactor
    const neckWidthFactor = doubleArrowDefualParam.neckWidthFactor
    const r = MathUtils.getBaseLength(t)
    const n = r * headHeightFactor
    const g = t[t.length - 1]
    const i = n * headWidthFactor
    const s = n * neckWidthFactor
    const a = n * neckHeightFactor
    const l = MathUtils.getThirdPoint(t[t.length - 2], g, 0, n, !0)
    const u = MathUtils.getThirdPoint(t[t.length - 2], g, 0, a, !0)
    const c = MathUtils.getThirdPoint(g, l, Constant.HALF_PI, i, !1)
    const p = MathUtils.getThirdPoint(g, l, Constant.HALF_PI, i, !0)
    const h = MathUtils.getThirdPoint(g, u, Constant.HALF_PI, s, !1)
    const d = MathUtils.getThirdPoint(g, u, Constant.HALF_PI, s, !0)

    return [h, c, g, p, d]
  }

  private static getArrowBodyPoints(
    t: IPoint2D[],
    o: IPoint2D,
    e: IPoint2D,
    r: number
  ) {
    const u: IPoint2D[] = []
    const c: IPoint2D[] = []

    for (
      let n = MathUtils.wholeDistance(t),
        g = MathUtils.getBaseLength(t),
        i = g * r,
        s = MathUtils.distance(o, e),
        a = (i - s) / 2,
        l = 0,
        p = 1;
      p < t.length - 1;
      p++
    ) {
      const h = MathUtils.getAngleOfThreePoints(t[p - 1], t[p], t[p + 1]) / 2
      l += MathUtils.distance(t[p - 1], t[p])
      const d = (i / 2 - (l / n) * a) / Math.sin(h)
      const f = MathUtils.getThirdPoint(t[p - 1], t[p], Math.PI - h, d, !0)
      const E = MathUtils.getThirdPoint(t[p - 1], t[p], h, d, !1)
      u.push(f), c.push(E)
    }

    return u.concat(c)
  }

  /**
   * @descripttion: 拍平二维数据
   * @param {Array} array
   * @return {*}
   */
  private static array2Dto1D<T, U>(array: Array<T>) {
    const newArray: Array<U> = []
    array.forEach((elt) => {
      newArray.push(elt[0])
      newArray.push(elt[1])
    })
    return newArray
  }

  /**
   * @descripttion: 解除复制
   * @param {Array<IPoint2D>} array
   * @return {*}
   */
  private static dereplication(array: IPoint2D[]) {
    const last = array[array.length - 1]
    let change = false
    let newArray: IPoint2D[] = []
    newArray = array.filter((i: IPoint2D) => {
      if (i[0] != last[0] && i[1] != last[1]) {
        return i
      }
      change = true
    })
    if (change) newArray.push(last)
    return newArray
  }

  private static getAttackArrowHeadPoints(
    t: IPoint2D[],
    o: IPoint2D,
    e: IPoint2D
  ) {
    const headHeightFactor = tailedAttackArrowDefualParam.headHeightFactor
    const headTailFactor = tailedAttackArrowDefualParam.headTailFactor
    const headWidthFactor = tailedAttackArrowDefualParam.headWidthFactor
    const neckWidthFactor = tailedAttackArrowDefualParam.neckWidthFactor
    const neckHeightFactor = tailedAttackArrowDefualParam.neckHeightFactor

    let r = MathUtils.getBaseLength(t)
    let n = r * headHeightFactor
    const g = t[t.length - 1]
    r = MathUtils.distance(g, t[t.length - 2])
    const i = MathUtils.distance(o, e)
    n > i * headTailFactor && (n = i * headTailFactor)
    const s = n * headWidthFactor
    const a = n * neckWidthFactor
    n = n > r ? r : n
    const l = n * neckHeightFactor
    const u = MathUtils.getThirdPoint(t[t.length - 2], g, 0, n, !0)
    const c = MathUtils.getThirdPoint(t[t.length - 2], g, 0, l, !0)
    const p = MathUtils.getThirdPoint(g, u, Constant.HALF_PI, s, !1)
    const h = MathUtils.getThirdPoint(g, u, Constant.HALF_PI, s, !0)
    const d = MathUtils.getThirdPoint(g, c, Constant.HALF_PI, a, !1)
    const f = MathUtils.getThirdPoint(g, c, Constant.HALF_PI, a, !0)
    return [d, p, g, h, f]
  }

  private static getAttackArrowBodyPoints(
    t: IPoint2D[],
    o: IPoint2D,
    e: IPoint2D,
    r: number
  ) {
    const u: IPoint2D[] = []
    const c: IPoint2D[] = []

    for (
      let n = MathUtils.wholeDistance(t),
        g = MathUtils.getBaseLength(t),
        i = g * r,
        s = MathUtils.distance(o, e),
        a = (i - s) / 2,
        l = 0,
        p = 1;
      p < t.length - 1;
      p++
    ) {
      const h = MathUtils.getAngleOfThreePoints(t[p - 1], t[p], t[p + 1]) / 2
      l += MathUtils.distance(t[p - 1], t[p])
      const d = (i / 2 - (l / n) * a) / Math.sin(h)
      const f = MathUtils.getThirdPoint(t[p - 1], t[p], Math.PI - h, d, !0)
      const E = MathUtils.getThirdPoint(t[p - 1], t[p], h, d, !1)
      u.push(f), c.push(E)
    }
    return u.concat(c)
  }

  private static getQBSplinePoints(t: IPoint2D[]) {
    if (t.length <= 2) return t
    const o = 2
    const e: IPoint2D[] = []
    const r = t.length - o - 1
    e.push(t[0])
    for (let n = 0; r >= n; n++) {
      for (let g = 0; g <= 1; g += 0.05) {
        let i = 0
        let y = 0
        for (let s = 0; o >= s; s++) {
          const a = this.getQuadricBSplineFactor(s, g);
          (i += a * t[n + s][0]), (y += a * t[n + s][1])
        }
        e.push([i, y])
      }
    }
    return e.push(t[t.length - 1]), e
  }

  private static getQuadricBSplineFactor(t: number, o: number) {
    return t == 0
      ? (o - 1) ** 2 / 2
      : t == 1
        ? (-2 * o ** 2 + 2 * o + 1) / 2
        : t == 2
          ? o ** 2 / 2
          : 0
  }
}
